/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package auxiliares;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * Histograma 1D
 *
 * @author jonas
 */
public class HistogramaNovo {

    private float larguraPanorama; //Utilizado na regra de 3 para converter ponto em grau
    private int[] conteudo;//Conteúdo do histograma (todos os valores)
    private double[] conteudoPesos;
    private float[] pontos;
    private int numAgrupador = 10; //Representa o valor para agrupar os graus. Ex.: 10 em 10 graus
    private double grauComMaisMatches = 0;
    private double grauComMaisMatchesSemONorte = 0;
    private int tamanhoMascara = 5;
    private String panoId;
    private double maiorNumeroMatchesMedia = 0;
    private double norte;
    private int numeroTotalMatches;
    private double[] mascara;

    public HistogramaNovo(String panoId, float larguraP, double norte) {

        this.panoId = panoId;
        this.larguraPanorama = (larguraP / 4);
        this.norte = norte;
        this.conteudo = new int[360 / numAgrupador];
        this.conteudoPesos = new double[360 / numAgrupador];
        this.mascara = criaMascara();

    }

    //Metodo que preenche o histograma com conteúdos
    public double criarHistograma() throws FileNotFoundException, IOException {
        lerPontos(this.panoId); //preeche o vetor PONTOS com os valores da coordenada X da imagem panoramica planificada do google
        for (float ponto : pontos) { // para cada ponto do vetor
            //incrementa no array, o número de pontos que cairam naquele grau
            conteudo[converterEmGraus(ponto) / numAgrupador] = conteudo[converterEmGraus(ponto) / numAgrupador] + 1;
        }

        /*
         Exemplo de ponto e ponto em graus
         ponto: 404.354
         ponto em graus: 20
         */
        calculaMaximo(); //CONFUSO

        converteGrauComBaseNorte();//CONFUSO

        /*grauComMaisMatches sem base Norte: 30.0
         grauComMaisMatches com base Norte: 247.77*/
       
        //salva histograma
        salvaHistograma();

        return maiorNumeroMatchesMedia;
    }

    /*Este Metodo le do arquivo de matches referentes ao panorama e inicializa
     * o array de pontos, com os valores encontrados nesse arquivo
     */
    private void lerPontos(String panoId) throws FileNotFoundException, IOException {

        BufferedReader buf = new BufferedReader(new FileReader(new File("temp/" + panoId + "_matches.txt")));

        numeroTotalMatches = Integer.parseInt(buf.readLine()); //le a primeira linha do arquivo, esta contem o numero total de matches
        pontos = new float[numeroTotalMatches]; //cria um vetor de pontos para armazenar todas as coordenadas X da foto panoramica onde foram encontrados matches de tamanho igual ao numero total de matches encontrados

        for (int i = 0; i < numeroTotalMatches; i++) {
            //le a linha i do arquivo de matches, mas só pega o primeiro numero, que é o referente a coordenada X da foto panoramica equirectangular do google e coloca no vetor de pontos
            String linha = buf.readLine();
            int index = linha.indexOf(" ");

            String valor = linha.substring(0, index);

            pontos[i] = Float.parseFloat(valor);
        }
    }

    //Regra de 3 para converter um ponto do eixo x, em grau
    private int converterEmGraus(float x) {
        //Caulca o grau o convertendo no eixo x (entre -1 e 1) e somando a rotacao (se necessario)
        int grau = (int) Math.toDegrees(Math.atan(converteOEspaco(x))) + (verificaLado(x) * 90);

        //AGRUPA ENTRE VALORES
        int grauAgrupado = (grau / numAgrupador) * numAgrupador;

        //Verifica se o numero e negativo, caso seja, utiliza o complementar a 360 pos se trata de um circulo
        return (grauAgrupado >= 0 ? grauAgrupado : 360 + grauAgrupado);
    }

    /*Este Metodo converte do espaco 0 a width para o espaco -1 a 1 utilizando interpolacao
     * alem disso, cria a rotacao, se necessario */
    private float converteOEspaco(float x) {
        return ((2 * (x - (larguraPanorama * verificaLado(x)))) - larguraPanorama) / larguraPanorama;
    }

    /*Este Metodo verifica quantos graus sao necessarios somar ao resultado final
     * dependendo do lado que estamos calculando
     */
    private int verificaLado(float x) {
        int posicaoLado;

        if (x < larguraPanorama) {
            posicaoLado = 0;
        } else {
            if ((x > larguraPanorama) && (x < (2 * larguraPanorama))) {
                posicaoLado = 1;
            } else {
                if ((x > (2 * larguraPanorama)) && (x < (3 * larguraPanorama))) {
                    posicaoLado = 2;
                } else {
                    posicaoLado = 3;
                }
            }
        }

        return posicaoLado;
    }

    public void calculaMaximo() {
//        criaMascara();
        for (int i = 0; i < conteudo.length; i++) {
            double soma = 0;
            int[] index = buscaListaIndex(i);
            for (int j = 0; j < mascara.length; j++) {
                soma = soma + (conteudo[index[j]] * mascara[j]);
            }
            conteudoPesos[i] = soma;
        }

        grauComMaisMatchesSemONorte = grauVencedor();
    }

    private static double[] criaMascara() {
        double[] pesosHistograma = {-2, -1, 0, +1, +2};
        double[] result = new double[pesosHistograma.length];
        double soma = 0.0;
        for (int i = 0; i < pesosHistograma.length; i++) {
            pesosHistograma[i] = (Math.exp(-(Math.pow(pesosHistograma[i], 2) / 2.0))) / Math.sqrt(2 * Math.PI);
            soma = soma + pesosHistograma[i];
        }

        for (int i = 0; i < pesosHistograma.length; i++) {
            result[i] = pesosHistograma[i] / soma;
        }

        return result;
    }

    public int[] buscaListaIndex(int index) {
        int[] resposta = new int[tamanhoMascara];

        double indexInicial = (index - 1 - mascara.length / 2 + 0.5);

        for (int i = 0; i < mascara.length; i++) {
            double j = (indexInicial + (i + 1));
            resposta[i] = mod(j, 360 / numAgrupador);
        }

        return resposta;
    }

    private static int mod(double a, double b) {
        double resto = (a % b) >= 0 ? (a % b) : b + (a % b);
        return (int) resto;
    }

    public double grauVencedor() {
        int index = -1;
        for (int i = 0; i < conteudoPesos.length; i++) {
            if (conteudoPesos[i] > maiorNumeroMatchesMedia) {
                maiorNumeroMatchesMedia = conteudoPesos[i];
                index = i;
            }
        }

        grauComMaisMatches = index * numAgrupador;
        return grauComMaisMatches;
    }

    private void converteGrauComBaseNorte() {
        double grauBaseNorte = (360 - norte) + grauComMaisMatches;
        grauComMaisMatches = grauBaseNorte < 360 ? grauBaseNorte : (grauBaseNorte - 360);
    }

    private void salvaHistograma() throws FileNotFoundException, IOException {
        File arq = new File("temp/" + panoId + "_histograma.txt");

        FileWriter fw = new FileWriter(arq);
        BufferedWriter bw = new BufferedWriter(fw);

        int grau = 0;
        for (int matche : conteudo) {
            bw.write(grau + ": " + matche);
            bw.newLine();
            grau = grau + numAgrupador;
        }

        bw.write("Numero Total de Matches: " + numeroTotalMatches);
        bw.newLine();
        bw.write("Norte: " + norte);
        bw.newLine();
//        bw.write("A conta para o calculo do angulo com maior numero de matches : "
//                + "(360 - Norte) + Angulo mais matches");
//        bw.newLine();
//        bw.write("360 - Norte = " + (350 - norte));
//        bw.newLine();
        bw.write("Angulo maior numero matches (sem o norte): " + grauComMaisMatchesSemONorte);
        bw.newLine();
        bw.write("Angulo maior numero matches : " + grauComMaisMatches);
        bw.close();
    }

    public int[] getConteudo() {
        return conteudo;
    }

    public void setConteudo(int[] conteudo) {
        this.conteudo = conteudo;
    }

    public float getLarguraPanorama() {
        return larguraPanorama;
    }

    public int getNumAgrupador() {
        return numAgrupador;
    }

    public void setNumAgrupador(int numAgrupador) {
        this.numAgrupador = numAgrupador;
    }

    public double getGrauComMaisMatches() {
        return grauComMaisMatches;
    }

    public void setGrauComMaisMatches(double grauComMaisMatches) {
        this.grauComMaisMatches = grauComMaisMatches;
    }

    public double getMaiorNumeroMatchesMedia() {
        return maiorNumeroMatchesMedia;
    }

    public void setMaiorNumeroMatchesMedia(double maiorNumeroMatchesMedia) {
        this.maiorNumeroMatchesMedia = maiorNumeroMatchesMedia;
    }

}
